Кэширование данных
==================

Кэширование данных — это сохранение некоторой переменной PHP в кэше и последующее её извлечение
оттуда. Для этой цели базовый класс компонентов кэширования [CCache]
предоставляет два наиболее используемых метода: [set()|CCache::set]
и [get()|CCache::get].

Для кэширования переменной `$value` мы выбираем уникальный идентификатор (ID)
и вызываем метод [set()|CCache::set] для её сохранения в кэше:

~~~
[php]
Yii::app()->cache->set($id, $value);
~~~

Данные будут оставаться в кэше до тех пор, пока не будут удалены
согласно некоторой политике кэширования (например, если места для хранения кэшированых
данных не осталось, тогда самые старые данные удаляются).
Чтобы изменить это поведение, мы можем установить срок действия кэша при вызове
метода [set()|CCache::set]. В этом случае данные будут удалены из кэша по истечении как максимум
заданного периода времени:

~~~
[php]
// храним значение переменной в кэше не более 30 секунд
Yii::app()->cache->set($id, $value, 30);
~~~

Позже, когда нам требуется обратиться к этой переменной (при обработке текущего или другого веб-запроса),
мы вызываем метод [get()|CCache::get] с указанным идентификатором, чтобы получить её значение из кэша.
Если будет возвращено значение false, то это означает, что переменная
не доступна в кэше, и мы должны заново создать её.

~~~
[php]
$value=Yii::app()->cache->get($id);
if($value===false)
{
	// устанавливаем значение $value заново, т.к. оно не найдено в кэше,
	// и сохраняем его в кэше для дальнейшего использования:
	// Yii::app()->cache->set($id,$value);
}
~~~

При выборе идентификатора для кэшируемой переменной учитывайте, что он
должен быть уникальным для каждой переменной из тех, что могут быть
кэшированы в приложении. *НЕ* требуется, чтобы идентификатор был уникальным
среди нескольких приложений, компонент кэширования достаточно умён для
различения идентификаторов разных приложений.

Некоторые кэш-хранилища, такие как MemCache и APC, поддерживают извлечение
нескольких кэшированных значений в пакетном режиме, что может уменьшить
накладные расходы при извлечении данных из кэша. Метод [mget()|CCache::mget]
позволяет использовать эту возможность. В случае если кэш-хранилище не поддерживает
такую возможность, [mget()|CCache::mget] будет тем не менее имитировать её.

Для удаления значения из кэша необходимо вызвать метод
[delete()|CCache::delete], а для очистки всего кэша — метод
[flush()|CCache::flush]. Следует быть осторожным при вызове метода
[flush()|CCache::flush], т.к. он также удаляет кэшированные данные
других приложений.

> Tip|Подсказка: класс [CCache] реализует интерфейс `ArrayAccess`, поэтому
> компонент кэширования может использоваться как массив. Ниже приведены примеры:
> ~~~
> [php]
> $cache=Yii::app()->cache;
> $cache['var1']=$value1;  // эквивалентно $cache->set('var1',$value1);
> $value2=$cache['var2'];  // эквивалентно $value2=$cache->get('var2');
> ~~~

Зависимость кэша
----------------

Помимо установки срока действия, кэшируемые данные также могут стать
недействительными в соответствии с некоторыми изменениями зависимости
(dependency).
Например, если мы кэшируем содержимое некоторого файла, и файл изменился,
мы должны считать кэшированную копию недействительной и извлечь свежее
содержимое из файла, а не из кэша.

Мы представляем зависимость как экземпляр класса [CCacheDependency] или
одного из его наследников. Мы передаём экземпляр зависимости вместе с
кэшируемыми данными, когда вызываем метод [set()|CCache::set].

~~~
[php]
// значение действительно не более 30 секунд
// кроме того, значение может стать недействительным раньше, если зависимый файл изменился
Yii::app()->cache->set($id, $value, 30, new CFileCacheDependency('FileName'));
~~~

Теперь, если мы попытаемся извлечь значение `$value` из кэша, вызвав метод
[get()|CCache::get], зависимость будет проверена и, если она изменилась, мы
получим значение false, означающее, что данные требуют обновления.

Ниже приведён список доступных зависимостей кэша:

   - [CFileCacheDependency]: зависимость меняется, если время последней модификации файла
   изменилось.

   - [CDirectoryCacheDependency]: зависимость меняется, если любой файл в
   каталоге или в подкаталогах изменился.

   - [CDbCacheDependency]: зависимость меняется, если результат запроса
   некоторого определённого SQL-выражения изменился.

   - [CGlobalStateCacheDependency]: зависимость меняется, если значение
   определённого глобального состояния изменилось. Глобальное состояние —
   это переменная, являющаяся постоянной в многократных запросах и сессиях
   приложения. Её значение устанавливается с помощью метода [CApplication::setGlobalState()].

   - [CChainedCacheDependency]: зависимость меняется, если любая зависимость
   цепочки изменилась.

   - [CExpressionDependency]: зависимость меняется, если результат
   определённого PHP выражения изменился.

Кэширование запросов
--------------------

Начиная с версии 1.1.7, Yii поддерживает кэширование запросов. Построенное на
кэшировании данных, кэширование запросов хранит результат запроса к базе
данных в кэше и, тем самым, экономит время, расходуемое на одни и те же запросы.

> Info|Информация: Некоторые СУБД, такие как
> [MySQL](http://dev.mysql.com/doc/refman/5.1/en/query-cache.html),
> поддерживают кэширование на стороне сервера базы данных. 
> Аналогичная возможность в Yii обеспечивает большую гибкость по сравнению с кэшированием на стороне сервера БД, и
> потенциально она более эффективна.


### Включение кэширования запросов

Для того чтобы включить кэширование запросов, убедитесь, что
[CDbConnection::queryCacheID] содержит ID подключённого компонента,
отвечающего за кэширование. По умолчанию это компонент `cache`.


### Использование кэширования запросов с DAO

Для того чтобы использовать кэширование запросов, необходимо вызвать
метод [CDbConnection::cache()], как показано ниже:

~~~
[php]
$sql = 'SELECT * FROM tbl_post LIMIT 20';
$dependency = new CDbCacheDependency('SELECT MAX(update_time) FROM tbl_post');
$rows = Yii::app()->db->cache(1000, $dependency)->createCommand($sql)->queryAll();
~~~

При выполнении приведённого кода Yii сначала проверит, есть ли в кэше актуальный
результат, соответствующий SQL-запросу, который мы собираемся выполнить. При этом
проверяются следующие три условия:

- есть ли в кэше данные с запросом в качестве индекса;
- не являются ли данные устаревшими (должно пройти менее 1000 секунд с момента последней записи в кэш);
- не изменилась ли зависимость кэша (максимальное значение `update_time` осталось тем же, каким
было при сохранении результата запроса в кэш).

Если все три условия выполнены, то результат берётся из кэша. Иначе выполняется
SQL-запрос, его результат записывается в кэш и возвращается.


### Использование кеширования запросов с ActiveRecord

Кэширование запросов также можно использовать совместно с [Active Record](/doc/guide/database.ar).
Для этого мы используем метод [CActiveRecord::cache()]:

~~~
[php]
$dependency = new CDbCacheDependency('SELECT MAX(update_time) FROM tbl_post');
$posts = Post::model()->cache(1000, $dependency)->findAll();
// реляционный запрос
$posts = Post::model()->cache(1000, $dependency)->with('author')->findAll();
~~~

Метод `cache()` является сокращением для вызова [CDbConnection::cache()].
При выполнении SQL-запроса, сгенерированного ActiveRecord, Yii попытается
использовать кэширование так же, как это было описано в предыдущем подразделе.


### Кэширование нескольких запросов

По умолчанию, каждый раз, когда мы вызываем метод `cache()` (класса [CDbConnection]
или [CActiveRecord]), он кэширует только следующий за его вызовом SQL-запрос. Все остальные
запросы НЕ кэшируются, пока мы не вызовем `cache()` ещё раз. Например:

~~~
[php]
$sql = 'SELECT * FROM tbl_post LIMIT 20';
$dependency = new CDbCacheDependency('SELECT MAX(update_time) FROM tbl_post');

$rows = Yii::app()->db->cache(1000, $dependency)->createCommand($sql)->queryAll();
// запрос НЕ БУДЕТ закэширован
$rows = Yii::app()->db->createCommand($sql)->queryAll();
~~~

Передавая методу `cache()` дополнительный параметр `$queryCount`, мы можем
закэшировать несколько подряд выполняющихся запросов. В следующем примере мы кэшируем
два запроса:

~~~
[php]
// ...
$rows = Yii::app()->db->cache(1000, $dependency, 2)->createCommand($sql)->queryAll();
// запрос БУДЕТ закэширован
$rows = Yii::app()->db->createCommand($sql)->queryAll();
~~~

Как известно, при выполнении реляционного AR-запроса, на самом деле могут выполняться
несколько SQL-запросов (это можно узнать,
проверив [журнал сообщений](/doc/guide/topics.logging)). Например, если
связь между `Post` и `Comment` типа `HAS_MANY`, то код, приведённый ниже,
выполнит два запроса:

- сначала будут выбраны 20 записей;
- после этого будут выбраны комментарии для этих записей.

~~~
[php]
$posts = Post::model()->with('comments')->findAll(array(
	'limit'=>20,
));
~~~

Если использовать кэширование запросов, как показано ниже, закэширован будет
только первый запрос к БД:

~~~
[php]
$posts = Post::model()->cache(1000, $dependency)->with('comments')->findAll(array(
	'limit'=>20,
));
~~~

Для того чтобы в кэш попали оба запроса, необходимо передать дополнительный
параметр, задающий количество кэшируемых запросов:

~~~
[php]
$posts = Post::model()->cache(1000, $dependency, 2)->with('comments')->findAll(array(
	'limit'=>20,
));
~~~


### Ограничения

Кэширование запросов не работает с результатами, содержащими указатели на ресурс.
Например, указатель возвращается в некоторых СУБД при использовании типа `BLOB`.

В некоторых хранилищах кэша есть ограничение на размер хранимых данных.
Например, в memcache максимальный размер одной единицы данных равен одному
мегабайту. Поэтому, если размер результата запроса превысит данное ограничение,
то кэширование не сработает.